#ifndef CLOTH_H
#define CLOTH_H

#include "vector"

#include "ngl/Transformation.h"
#include "ngl/TransformStack.h"
#include "ngl/Obj.h"
#include "ngl/BBox.h"

#include "ShaderManager.h"
#include "Particle.h"
#include "Spring.h"
#include "ClothMaterial.h"
#include "ObstacleManager.h"


/// @enum IntegrationType Integration Method
enum IntegrationType
{
    EULAR_FORWARD,      ///< eular forward explicit
    VERLET              ///< verlet
};

/// @enum ConstrainedScenario Scenario
enum ConstrainedScenario
{
    TABLECLOTH,         ///< table cloth - unpinned horizontal
    LIGHT_CURTAIN,      ///< curtain with 2 pins at extremities
    SOLID_CURTAIN,      ///< full first row pinned
    SHELTER,            ///< horizontal 4 pins at extremities
    FLAG,               ///< flag with 2 pins at extremities - vertical
    BANNER              ///< banner with 2 columns pinned at extremities - vertical
};

/// @file Cloth.h
/// @brief represents the main cloth structure and takes care of the cloth movement and interaction
/// @author Perseedoss Rajiv
/// @version 1.0
/// @date 12.04.11
/// Revision History :
/// Initial Version 12.04.11
/// @class Cloth
/// @brief the main cloth that that creates the internal mass and springs and manage them

class Cloth
{
public:
    /// @brief ctor
    /// @param[in] _height height/depth of the cloth
    /// @param[in] _width width of the cloth
    /// @param[in] _verticalParticleCount the number of particles in the vertical direction of the cloth
    /// @param[in] _horizontalParticleCount the number of particles in the horizontal direction of the cloth
    /// @param[in] _mass the mass of the particles
    Cloth
        (
            const int _height,
            const int _width,
            const int _verticalParticleCount,
            const int _horizontalParticleCount,
            const float _mass
        );

    /// @brief dtor
    ~Cloth();

    /// @brief update the position of particles at each timestep interval
    /// @param[in] _obstacleManager the obstacle manager that holds the list of obstacles
        void updateParticles(ObstacleManager *_obstacleManager);

    /// @brief re initialise cloth to its default one with the current parameters and a specific scenario
    /// @param[in] _height new height of the cloth
    /// @param[in] _width new width of the cloth
    /// @param[in] _verticalParticleCount the modified number of particles in the vertical direction of the cloth
    /// @param[in] _horizontalParticleCount the modified number of particles in the horizontal direction of the cloth
    /// @param[in] _timestep the modified timestep of simulation
    /// @param[in] _integrationType the new integration method
    /// @param[in] _scenario the simulation scenario to apply next
    void reInitialiseParticles
                        (
                            const int _height,
                            const int _width,
                            const int _verticalParticleCount,
                            const int _horizontalParticleCount,
                            const float _timestep,
                            const IntegrationType _integrationType,
                            const ConstrainedScenario _scenario
                        );

    /// @brief re initialise cloth to its default one with the current parameters, keeping the current scenario
    /// @param[in] _height new height of the cloth
    /// @param[in] _width new width of the cloth
    /// @param[in] _verticalParticleCount the modified number of particles in the vertical direction of the cloth
    /// @param[in] _horizontalParticleCount the modified number of particles in the horizontal direction of the cloth
    /// @param[in] _timestep the modified timestep of simulation
    /// @param[in] _integrationType the new integration method
    void reInitialiseParticles
                        (
                            const int _height,
                            const int _width,
                            const int _verticalParticleCount,
                            const int _horizontalParticleCount,
                            const float _timestep,
                            const IntegrationType _integrationType
                        );

    /// @brief creates vbo for drawing
    void loadGeometry();

    /// @brief draws the particles
    /// @param[in] _txStack the transform stack, used for pushing and popping opengl states
    /// @param[in,out] io_sman the shader manager, used for shading/rendering of the objects
    /// @param[in] _wireframeShader the wireframe shader used for particle drawing
    /// @param[in] _textureShader the texture shader used for shaded drawing
    void draw
            (
                ngl::TransformStack _txStack,
                ShaderManager* io_sman,
                const std::string _wireframeShader,
                const std::string _textureShader
            );

    /// @brief sets whether internal trace is enabled
    /// @param[in] _state flag to indicate whether trace is enabled;
    inline void setEnableInternalTrace(const bool _state) { m_enableInternalTrace = _state; }

    /// @brief returns whether internal tracing is enabled
    inline bool getEnableInternalTrace() const { return m_enableInternalTrace; }

    /// @brief sets whether internal trace with neighbours is enabled
    /// @param[in] _state flag to indicate whether trace is enabled;
    inline void setEnableInternalTraceNeighbours(const bool _state) { m_enableInternalTraceNeighbours = _state; }

    /// @brief returns whether internal tracing with neighbours is enabled
    inline bool getEnableInternalTraceNeighbours() const { return m_enableInternalTraceNeighbours; }

    /// @brief sets the id of the particle under internal tracing
    /// @param[in] _val the particle id that will currently be traced
    inline void setInternalTraceParticleId(const int _val) { m_internalTraceParticleId = _val; }

    /// @brief returns the id of the particle that is currently being traced
    inline int getInternalTraceParticleId() const { return m_internalTraceParticleId; }

    /// @brief returns the particle that is currently traced
    inline Particle* getInternalTraceParticle() const { return (*m_particleList)[m_internalTraceParticleId]; }

    /// @brief toggle pin detail for the current particle being traced
    void pinInternalTraceParticle(const bool _toBePinned);

    /// @brief sets the number of vertical particles
    /// @param[in] _count the count of vertical particles
    inline void setVerticalParticleCount(const int _count) { m_verticalParticleCount = _count; }

    /// @brief returns the count of particle in the vertical direction
    inline int getVerticalParticleCount() const { return m_verticalParticleCount; }

    /// @brief sets the number of horizontal particles
    /// @param[in] _count the count of horizontal particles
    inline void setHorizontalParticleCount(const int _count) { m_horizontalParticleCount = _count; }

    /// @brief returns the count of particle in the horizontal direction
    inline int getHorizontalParticleCount() const { return m_horizontalParticleCount; }

    /// @brief returns wherether automatic cloth movement is enabled
    inline bool getEnableAutoCloth() const { return m_enableAutoCloth; }

    /// @brief toggles the state of the automatic cloth movement flag
    inline void toggleEnableAutoCloth() { m_enableAutoCloth ^= true; }

    /// @brief returns the integration algorithm for the simulation
    inline IntegrationType getIntegrationType() const { return m_integrationType; }

    /// @brief sets the integration method used for the simulation update
    /// @param[in] _type the integration type
    inline void setIntegrationType(const IntegrationType _type) { m_integrationType = _type; }

    /// @brief returns the wireframe toggle value
    inline bool getDrawWireframe() { return m_drawWireframe; }

    /// @brief sets the flag for wireframe toggle for drawing
    /// @param[in] _value the wireframe toggle flag
    inline void setDrawWireframe(const bool _value) { m_drawWireframe = _value; }

    /// @brief sets the material for the current cloth
    /// @param[in] _name the material selected
    inline void setCurrentClothMaterial(const std::string _name)
    {
        m_currentClothMaterial = getClothMaterialByName(_name, m_currentClothMaterialIndex, m_stiffness, m_damping);
    }

    /// @brief returns the current material name
    inline std::string getCurrentClothMaterialName() { return m_currentClothMaterial->getName(); }

    /// @brief returns the current material object
    inline ClothMaterial* getCurrentClothMaterial() { return m_currentClothMaterial; }

    /// @brief returns the current material index
    inline int getCurrentClothMaterialIndex() { return m_currentClothMaterialIndex; }

    /// @brief returns the current stiffness of the simulation
    inline float getStiffness() const { return m_stiffness; }

    /// @brief sets the stiffness for the cloth material
    /// @param[in] _v the stiffness value
    inline void setStiffness(const float _v) { m_stiffness = _v; }

    /// @brief returns the current damping coefficient of the simulation
    inline float getDamping() { return m_damping; }

    /// @brief sets the damping for the cloth material
    /// @param[in] _v the damping coefficient
    inline void setDamping(const float _v) { m_damping = _v; }

    /// @brief sets the height of the cloth
    /// @param[in] _v the height
    inline void setHeight(const int _v) { m_height = _v; }

    /// @brief returns the height of the cloth
    inline int getHeight() { return m_height; }

    /// @brief sets the width of the cloth
    /// @param[in] _v the width
    inline void setWidth(const int _v) { m_width = _v; }

    /// @brief returns the width of the cloth
    inline int getWidth() { return m_width; }

    /// @brief sets the timestep of the cloth simulation
    /// @param[in] _v the timestep
    inline void setTimestep(const float _v) { m_timestep = _v; }

    /// @brief returns the timestep of the simulation
    inline float getTimestep() { return m_timestep; }

    /// @brief sets the toggle for gravity force
    /// @param[in] _v the gravity toggle flag value
    inline void setEnableGravity(const bool _v) { m_enableGravity = _v; }

    /// @brief returns the toggle value for gravity
    inline bool getEnableGravity() { return m_enableGravity; }

    /// @brief sets the toggle for wind force
    /// @param[in] _v the wind toggle flag value
    inline void setEnableWind(const bool _v) { m_windEnabled = _v; }

    /// @brief returns the toggle value for wind
    inline bool getEnableWind() { return m_windEnabled; }

    /// @brief returns the wind strength
    inline float getWindStrength() { return m_windStrength; }

    /// @brief sets the wind strength
    /// @param[in] _v the strength
    inline void setWindStrength(const float _v) { m_windStrength = _v; }

    /// @brief returns the wind direction
    inline ngl::Vector getWindDirection() { return m_windDirection; }

    /// @brief sets the wind direction
    /// @param[in] _v the direction
    inline void setWindDirection(const ngl::Vector _v) { m_windDirection = _v; }

    /// @brief sets the toggle for structural spring of the simulation
    /// @param[in] _v the structural toggle value
    inline void setEnableHookeStructural(const bool _v) { m_enableHookeStructural = _v; }

    /// @brief returns the toggle value for structural spring of the simulation
    inline bool getEnableHookeStructural() { return m_enableHookeStructural; }

    /// @brief sets the toggle for stretch spring of the simulation
    /// @param[in] _v the stretch toggle value
    inline void setEnableHookeStretch(const bool _v) { m_enableHookeStretch = _v; }

    /// @brief returns the toggle for the stretch spring of the simulation
    inline bool getEnableHookeStretch() { return m_enableHookeStretch; }

    /// @brief sets the toggle for bend spring of the simulation
    /// @param[in] _v the bend toggle value
    inline void setEnableHookeBend(const bool _v) { m_enableHookeBend = _v; }

    /// @brief returns the toggle for bend spring of the simulation
    inline bool getEnableHookeBend() { return m_enableHookeBend; }

    /// @brief toggle the pin value for all the particles
    /// @param[in] _toBePinned the pin toggle value
    void pinAll(const bool _toBePinned);

    /// @brief toggle the pin value for a row
    /// @param[in] _toBePinned the pin toggle value
    void pinRow(const bool _toBePinned);

    /// @brief toggle the pin value for a column
    /// @param[in] _toBePinned the pin toggle value
    void pinColumn(const bool _toBePinned);

    /// @brief returns the current scenario of the simulation
    inline ConstrainedScenario getScenario() { return m_scenario; }

private:
    /// @brief the masses affecting the cloth simulation
    std::vector<Particle*>* m_particleList;

    /// @brief the particles arranged in quad
    std::vector<Particle*>* m_particleInQuadList;

    /// @brief the springs connecting the masses
    std::vector<Spring*>* m_springList;

    /// @brief the positions of the masses used in the vertex array for drawing
    std::vector<ngl::Vector> m_particlePositionList;

    /// @brief the positions of quads of masses used in the vertex array for drawing
    std::vector<ngl::Vector> m_particleQuadPositionList;

    /// @brief the texture coordinates of quads used in shaded mode drawing
    std::vector<GLfloat> m_textureCoords;

    /// @brief the initial height of the cloth
    float m_initialClothHeight;

    /// @brief flag to toggle wireframe mode
    bool m_drawWireframe;

    /// @brief the height of cloth
    int m_height;

    /// @brief the width of cloth
    int m_width;

    /// @brief the vertical number of particles in the cloth
    int m_verticalParticleCount;

    /// @brief the horizontal number of particles in the cloth
    int m_horizontalParticleCount;

    /// @brief the step value in the column horizontal direction
    float m_xStep;

    /// @brief the step value in the row vertical direction
    float m_zStep;

    /// @brief the particle mass
    float m_particleMass;

    /// @brief the particle radius
    float m_particleRadius;

    /// @brief the timestep of the simulation
    float m_timestep;

    /// @brief the stiffness of the cloth
    float m_stiffness;

    /// @brief the damping coefficient of the cloth
    float m_damping;

    /// @brief the toggle for gravity
    bool m_enableGravity;

    /// @brief the toggle for wind
    bool m_windEnabled;

    /// @brief the wind strength
    float m_windStrength;

    /// @brief the wind direction
    ngl::Vector m_windDirection;

    /// @brief the toggle for structural springs
    bool m_enableHookeStructural;

    /// @brief the toggle for stretch springs
    bool m_enableHookeStretch;

    /// @brief the toggle for bend springs
    bool m_enableHookeBend;

    /// @brief the maximum weight of the last row
    int m_maxLastRowWeight;

    /// @brief the number of steps to apply weighted cloth
    int m_weightSteps;

    /// @brief flag to determine if we are tracing a particle
    bool m_enableInternalTrace;

    /// @brief the id of the particle that is being traced
    int m_internalTraceParticleId;

    /// @brief flag to determine if we are tracing neighbours
    bool m_enableInternalTraceNeighbours;

    /// @brief flag to control automatic movement of the cloth
    bool m_enableAutoCloth;

    /// @brief integration method used for the simulation
    IntegrationType m_integrationType;

    /// @brief the structural horizontal rest length
    float m_structuralHorizontalRestLength;

    /// @brief the structural vertical rest length
    float m_structuralVerticalRestLength;

    /// @brief the shear rest length
    float m_shearRestLength;

    /// @brief the bend horizontal rest length
    float m_flexHorizontalRestLength;

    /// @brief the bend horizontal rest length
    float m_flexVerticalRestLength;

    /// @brief the number of textures loaded
    int m_textureCount;

    /// @brief the texture width
    int m_textureWidth;

    /// @brief the texture height
    int m_textureHeight;

    /// @brief the texture handlers
    std::vector<GLuint> m_textureHandler;

    /// @brief the list of cloth materials
    std::vector<ClothMaterial*>* m_clothMaterialList;

    /// @brief the currently selected cloth material
    ClothMaterial* m_currentClothMaterial;

    /// @brief the index of the currently selected cloth material
    int m_currentClothMaterialIndex;

    /// @brief the scenario of the simulation
    ConstrainedScenario m_scenario;

private:
    /// @brief delete particles structural horizontal rest length
    void deleteParticles();

    /// @brief setup static constraints and pinning based on current scenarios
    void initialStaticConstraints();

    /// @brief creates, connect and position particles
    void createAndConnectParticles();

    /// @brief create particles
    void createParticles();

    /// @brief position particles in space
    /// @param[in] _isHorizontal the default orientation of the cloth
    void positionParticles(const bool _isHorizontal = true);

    /// @brief connect particles through springs
    void connectParticles();

    /// @brief refresh and update the neighbours of particles
    void refreshParticleNeighbours();

    /// @brief evaluate net force on the particles
    void evaluateNetForces();

    /// @brief integrate a particle using eular explicit
    /// @param[in,out] io_currentParticle the particle that is being affected
    void evaluateEularExplicit(Particle* io_currentParticle);

    /// @brief integrate a particle using verlet
    /// @param[in,out] io_currentParticle the particle that is being affected
    void evaluateVerlet(Particle* io_currentParticle);

    /// @brief integrate next velocity and position of a particle
    /// @param[in,out] io_currentParticle the particle that is being affected
    void integrateNext(Particle* io_currentParticle);

    /// @brief integrate backward when collsion occurs to maintain continuity in
    /// velocity and position
    /// @param[in,out] io_currentParticle the particle that is being affected
    /// @param[in] _newPosition the new position to integrate from
    void integrateBackward
                (
                    Particle* io_currentParticle,
                    const ngl::Vector _newPosition);

    /// @brief check if collision occured by a particle
    /// @param[in] _obstacleManager the obstacle manager containing all the obstacles
    /// @param[in] _currentParticle the particle being checked upon
    /// @param[out] o_newPosition the safe position in case collision occurs
    bool isCollisionDetected
                        (
                            ObstacleManager *_obstacleManager,
                            Particle* _currentParticle,
                            ngl::Vector &o_newPosition
                        );

    /// @brief determine the colour to be applied to a particle
    /// @param[in] _particle the particle whose colour is to be set
    ngl::Colour determineParticleColor(const Particle* _particle);

    /// @brief check if a particle is a neighbour of a center particle under consideration
    /// and returns a specific colour based on the type of spring
    /// @param[in] _mainId the id of the center particle, who has the neighbour list
    /// @param[in] _testId the id of the particle that we are checking in the neighbour list
    ngl::Colour determineIfIsANeighbourAndGetColour
                                (
                                    const int _mainId,
                                    const int _testId
                                );

    /// @brief connect a specific spring between 2 particles for hookes law calculations
    /// @param[in] _mainId the main particle
    /// @param[in] _neighbourId the neighbouring particle
    /// @param[in] _length the rest length of the spring
    /// @param[in] _springType the type of the spring
    void createGlobalSpring
                                (
                                    const int _mainId,
                                    const int _neighbourId,
                                    const float _length,
                                    const SpringType _springType
                                );

    /// @brief connect springs between 2 particles
    /// @param[in] _mainId the main particle
    /// @param[in] _neighbourId the neighbouring particle
    void createAndAddSpringToParticle
                                (
                                    const int _mainId,
                                    const int _neighbourId
                                );

    /// @brief returns an id from z,x coordinates
    /// @param[in] _z z coordinate
    /// @param[in] _x x coordinate
    int getIdFromCoordinates
                        (
                            const int _z,
                            const int _x
                        );

    /// @brief get z,x coordinates given an id
    /// @param[in] _id id to be translated into z,x
    /// @param[out] o_z z coordinate
    /// @param[out] o_x x coordinate
    void getZXCoordinatesFromId
                        (
                            const int _id,
                            int &o_z,
                            int &o_x
                        );

    /// @brief determine spring force in a spring
    /// @param[out] o_spring spring to be modified with hookes force
    float determineInternalSpringForce(Spring* o_spring);

    /// @brief setup the particles in quads for drawing later on
    void setupQuadsForSolidDrawing();

    /// @brief calculates a triangle normal
    /// @param[in] _p1 point 1 of the triangle
    /// @param[in] _p2 point 2 of the triangle
    /// @param[in] _p3 point 3 of the triangle
    ngl::Vector calculateTriangleNormal
                                (
                                    Particle* _p1,
                                    Particle* _p2,
                                    Particle* _p3
                                );

    /// @brief calculates wind on a triangle and add the force to the particles
    /// @param[in,out] io_p1 point 1 of the triangle
    /// @param[in,out] io_p2 point 2 of the triangle
    /// @param[in,out] io_p3 point 3 of the triangle
    /// @param[in] _direction the direction of th ewind
    void addWindForceOnTriangle
                        (
                            Particle* io_p1,
                            Particle* io_p2,
                            Particle* io_p3,
                            const ngl::Vector _direction
                        );

    /// @brief reads an image file and return data
    /// @param[in] _filename the name of the image
    unsigned char* loadimgdata(const std::string _filename);

    /// @brief create textures, loading them from file
    void createTextures();

    /// @brief returns the default cloth material object
    ClothMaterial* getDefaultClothMaterial();

    /// @brief returns a cloth material object and its index, given a name
    /// @param[in] _name the name of the material to search for
    /// @param[out] o_index the index of the searched material
    /// @param[out] o_stiffness the stiffness of the searched material
    /// @param[out] o_damping the damping of the searched material
    ClothMaterial* getClothMaterialByName
                            (
                                const std::string _name,
                                int &o_index,
                                float &o_stiffness,
                                float &o_damping
                            );

    /// @brief initialise cloth parameters
    /// @param[in] _height height of the cloth
    /// @param[in] _width width of the cloth
    /// @param[in] _verticalParticleCount the number of particles in the vertical direction of the cloth
    /// @param[in] _horizontalParticleCount the number of particles in the horizontal direction of the cloth
    /// @param[in] _timestep the timestep of simulation
    /// @param[in] _integrationType the integration method
    void initialise
            (
                const int _height,
                const int _width,
                const int _verticalParticleCount,
                const int _horizontalParticleCount,
                const float _timestep,
                const IntegrationType _integrationType
            );

    /// @brief pin a specific row
    /// @param[in] _toBePinned toggle of the pin state
    /// @param[in] _row row to pin
    void pinRow
            (
                const bool _toBePinned,
                const int _row
            );

    /// @brief pin a specific column
    /// @param[in] _toBePinned toggle of the pin state
    /// @param[in] _column column to pin
    void pinColumn
            (
                const bool _toBePinned,
                const int _column
            );
};

#endif // CLOTH_H
